/*
 * =====================================================================================
 *
 *       Filename:  memdev.c
 *
 *    Description:  通过内存虚拟出一个字符设备的驱动程序
 *    添加并发控制
 *    向一个环形内存里写数据
 *    添加ioctl接口
 *
 *        Version:  1.0
 *        Created:  2015年09月17日 19时42分26秒
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  YOUR NAME (), 
 *   Organization:  
 *
 * =====================================================================================
 */

#include <linux/module.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include "memdev.h"

/**
 * @brief 定义全局变量
 */
static int mem_major = MEMDEV_MAJOR;
struct mem_dev *mem_devp;
struct cdev cdev;

/**
 * @brief 定义模块参数
 *
 * @param mem_major
 * @param int
 * @param S_IRUGO
 */
module_param(mem_major,int,S_IRUGO);

/**
 * @brief 定义打开操作
 * 目标就是让file指针与设备信息关联起来
 *
 * @param inode
 * @param filp
 *
 * @return 
 */
int mem_open(struct inode *inode ,struct file *filp)
{
    struct mem_dev *dev;

    /* 获得设备号*/
    int num = MINOR(inode->i_rdev);
    if(num >= MEMDEV_NR_DEVS)
       return -ENODEV;
    printk(KERN_INFO"dev no:%d\n",num);
    //由次设备号推算出对应哪个内存虚拟设备
    dev = &mem_devp[num];
    //将该内存虚拟设备的结构体放入filp的private_data域,让设备与file指针关联起来，便于后面其它操作
    filp->private_data = dev;
    return 0;
}

/**
 * @brief 定义关闭设备的操作
 *
 * @param inode
 * @param filp
 *
 * @return 
 */
int mem_release(struct inode *inode ,struct file *filp)
{
    return 0;
}

/**
 * @brief 定义读操作
 *
 * @param filp
 * @param buf
 * @param size
 * @param ppos
 *
 * @return 
 */
static ssize_t mem_read(struct file *filp , char __user *buf , size_t size , loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;

    //由file指针得到对应的设备对象指针
    struct mem_dev *dev = filp->private_data;
    // 获取信号量
    if(down_interruptible(&dev->sema))
        return -ERESTARTSYS;


    //判断位置p的合理性
    if (p >= MEMDEV_SIZE)
       return 0;
    if(count > MEMDEV_SIZE - p)
       count = MEMDEV_SIZE - p ;
    //将数据返回给用户空间
    if(copy_to_user(buf , (void*)(dev->data + p),count)){
        ret = -EFAULT;

    }else{
        //更新偏移量
        *ppos += count;
        ret = count;
        printk(KERN_INFO "read %d bytes from %ld\n",count , p); 
    }
    //释放信号量
    up(&dev->sema);
    return ret;
} 

/**
 * @brief 定义写操作
 *
 * @param filp
 * @param buf
 * @param size
 * @param ppos
 *
 * @return 
 */
static ssize_t mem_write(struct file *filp , const char __user *buf , size_t size , loff_t *ppos)
{
#define NOSEM
    unsigned long p = *ppos;
    unsigned int count = size;
    char tmp[MEMDEV_SIZE];
   int ret = 0;
   int i;
   //由file指针得到对应的设备对象指针
   struct mem_dev *dev = filp->private_data;
   printk(KERN_INFO"write start...");
#ifndef NOSEM

   printk(KERN_INFO" have semaphore ...\n");
   if(down_interruptible(&dev->sema))
       return -ERESTARTSYS;
#else
   printk(KERN_INFO" have no  semaphore ...\n");

#endif
   memset(tmp,0,MEMDEV_SIZE);

   //从用户空间复制数据到tmp下,再依次放入data内存里
   if(copy_from_user(tmp , buf, count))
      ret = -EFAULT;
   else{
       for (i = 0; i < size; ++i) {
           dev->data[dev->wpos] = tmp[i];
           //环形内存空间，到结尾处返回头部
           if(dev->wpos < MEMDEV_SIZE - 1)
               dev->wpos += 1;
           else
              dev->wpos = 0;
          msleep(1); 
           
       }
   } 
#ifndef NOSEM
  up(&dev->sema);
#endif 
   return ret;
}

/**
 * @brief 定义查询定位操作
 * 处理各种定位情况下的偏移量值
 * @param filp
 * @param offset
 * @param whence
 *
 * @return 
 */
static loff_t mem_llseek(struct file *filp , loff_t offset , int whence)
{
    loff_t newpos;
    switch (whence) {
        case 0:
            newpos = offset;
            break;
        case 1:
            newpos = filp->f_pos + offset;
            break;
        case 2:
            newpos = MEMDEV_SIZE -1  + offset;
            break;
        default:
            return -EINVAL;
    } 
   if((newpos < 0) || (newpos > MEMDEV_SIZE))
       return -EINVAL;
   filp->f_pos = newpos;
   return newpos;
} 
static int mem_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{
    int err = 0;
    int ret = 0;
    int ioarg = 0;

    if(_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC)
        return -EINVAL;
    if(_IOC_NR(cmd) > MEMDEV_IOC_MAXNR)
        return -EINVAL;

    if(_IOC_DIR(cmd) & _IOC_READ)
        err = !access_ok(VERIFY_WRITE,(void *)arg,_IOC_SIZE(cmd));
    else if(_IOC_DIR(cmd) & _IOC_WRITE)
        err = !access_ok(VERIFY_READ,(void *)arg , _IOC_SIZE(cmd));
    if(err)
        return -EINVAL;

    switch (cmd) {
        case MEMDEV_IOCPRINT:
            printk ("<--- CMD MEMDEV_IOCPRINT Done --->\n\n");
            break;
        case MEMDEV_IOCGETDATA:
            ioarg = filp->f_pos;
            ret = __put_user(ioarg,(int *)arg);
            break;
        case MEMDEV_IOCSETDATA:
            ret = __get_user(ioarg,(int *)arg);
            if((ioarg <0)||(ioarg > MEMDEV_SIZE))
                return -EINVAL;
            filp->f_pos = ioarg;
            printk ("<--- In Kernel  MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg);
            break;
        default:
           return -EINVAL; 
            
    }

}


/**
 * @brief 定义操作集合
 */
static const struct file_operations mem_fops =
{
    .owner = THIS_MODULE,
    .llseek = mem_llseek,
    .read = mem_read,
    .write = mem_write,
    .open = mem_open,
    .release = mem_release,
    .unlocked_ioctl = mem_ioctl 
};

/**
 * @brief 模块初始化过程
 *
 * @return 
 */
static int __init memdev_init(void)
{
    int result;
    int i;
    //静态申请设备号
    dev_t devno = MKDEV(mem_major , 0);
    if(mem_major)
        result = register_chrdev_region(devno ,2,"memdev");
    else{//动态申请设备号
        result = alloc_chrdev_region(&devno , 0, 2 , "memdev");
        mem_major = MAJOR(devno);
    }
   if(result < 0)
      return result;

   // 初始化cdev结构体
   cdev_init(&cdev , &mem_fops);
   cdev.owner  = THIS_MODULE;
   //注册字符设备
   cdev_add(&cdev , MKDEV(mem_major , 0),MEMDEV_NR_DEVS);
   //初始化模拟设备
   mem_devp = kmalloc(MEMDEV_NR_DEVS*sizeof(struct mem_dev) , GFP_KERNEL);
   if(!mem_devp)
   {
       result = -ENOMEM;
       goto fail_malloc; 
   } 
   memset(mem_devp , 0 , sizeof(struct mem_dev));
   //为多个设备分配内存空间,并初始化变量
   for (i = 0; i < MEMDEV_NR_DEVS; ++i) {
       mem_devp[i].size = MEMDEV_SIZE;
       mem_devp[i].data = kmalloc(MEMDEV_SIZE , GFP_KERNEL);
       memset(mem_devp[i].data , 0 , MEMDEV_SIZE); 
       sema_init(&mem_devp[i].sema,1);
       mem_devp[i].wpos = 0;
   }
    return 0;
fail_malloc:
   unregister_chrdev_region(devno,1);
   return result; 
}

/**
 * @brief 模块退出过程
 *
 * @return 
 */
static void __exit memdev_exit(void)
{
   cdev_del(&cdev);
   kfree(mem_devp);
   unregister_chrdev_region(MKDEV(mem_major , 0),2); 
}
MODULE_AUTHOR("guicai.ma@qq.com");
MODULE_LICENSE("GPL");
module_init(memdev_init);
module_exit(memdev_exit);
